﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;


namespace MishraReader.Entities
{
    [DataContract]
    [DebuggerDisplay("Title = {_title}, LocalUnread = {_localReadCount}, ServerUnread = {_serverUnreadsCount}")]
    public class Subscription : Notifier, IGoogleReaderStream
    {
        [DataMember(Name = "categories")]
        private List<SubscriptionCategory> _categories;

        private bool _isEditing;
        private bool _isMarkingAllRead;
        private int _localReadCount;
        private int _serverUnreadsCount;

        private Dictionary<Item, PropertyObserver<Item>> _itemObservers;

        private string _title;

        public IEnumerable<SubscriptionCategory> Categories
        {
            get { return _categories; }
        }

        public SubscriptionCategory Category
        {
            get { return Categories.FirstOrDefault(); }
        }

        public string Feed
        {
            get { return Id.Substring(5); }
            set { Id = "feed/" + value; }
        }

        public bool HasItems
        {
            get { return Items.Count > 0; }
        }

        public bool HasStarred
        {
            get { return StarredItems.Any(); }
        }

        public bool HasUnreads
        {
            get { return UnreadsCount > 0; }
        }

        [DataMember(Name = "id")]
        public string Id { get; set; }

        IEnumerable<Subscription> IGoogleReaderStream.Subscriptions
        {
            get
            {
                yield return this;
            }
        }

        string IGoogleReaderStream.Label
        {
            get { return Title; }
        }

        int IGoogleReaderStream.UnreadCount
        {
            get { return UnreadsCount; }
        }

        public ObservableCollection<Item> Items { get; private set; }

        public int LocalReadCount
        {
            get { return _localReadCount; }
            //set
            //{
            //    //if (_localReadCount != value || _localReadCount == 0)
            //    //{
            //        _localReadCount = value;

            //    //    foreach (var category in Categories)
            //    //        category.RaiseUnreadsCountChanged();

            //    //    SubscriptionsList.RaiseUnreadsCountChanged();
            //    //}
            //}
        }

        

        public Int64 NewestItemTimestampUsec { get; set; }

        public bool RefreshRequired { get; set; }

        public int ServerUnreadsCount
        {
            get { return _serverUnreadsCount; }
            set
            {
                _localReadCount = 0;

                ServerUnreadsCountChanged = true;
                _serverUnreadsCount = value;

                //SubscriptionsList.RaiseUnreadsCountChanged();

                RaiseUnreadsChanged();
            }
        }

        private void RaiseUnreadsChanged()
        {
            RaisePropertyChanged(() => HasUnreads);
            RaisePropertyChanged(() => UnreadsCount);
        }

        public bool ServerUnreadsCountChanged { get; set; }

        public IEnumerable<Item> StarredItems
        {
            get { return Items.Where(i => i.Starred); }
        }

        public SubscriptionsList SubscriptionsList { get; private set; }

        [DataMember(Name = "title")]
        public string Title
        {
            get { return _title; }
            set
            {
                Set(ref _title, value);
            }
        }

        public IEnumerable<Item> UnreadItems
        {
            get { return Items == null ? Enumerable.Empty<Item>() : Items.Where(i => i.Unread && !i.Starred); }
        }

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(ServerUnreadsCount - LocalReadCount >= 0);
        }
        
        public void MarkAllAsRead(bool lockState)
        {
            _isMarkingAllRead = true;
            try
            {
                foreach (var item in UnreadItems.ToList())
                {
                    item.MarkAsRead();
                }

                // Once mark all as read has happened, all previous items are locked
                if (lockState)
                {
                    Items.ForEach(i => i.IsReadStateLocked = true);
                }

                // Reset to 0
                ServerUnreadsCount = 0;
            }
            finally
            {
                _isMarkingAllRead = false;
            }

            RaiseUnreadsChanged();
        }

        public int UnreadsCount
        {
            get { return ServerUnreadsCount - LocalReadCount; }
        }


        public void AddItem(Item item, LoadItemType loadItemType)
        {
            Contract.Requires<InvalidOperationException>(SubscriptionsList != null, "Call Initialize first");
            Contract.Requires<ArgumentNullException>(item != null, "item");

            item.Subscription = this;

            //var isStarred = item.Starred;

            var existingItem = Items.FirstOrDefault(i => i.Id == item.Id);
            if (existingItem != null)
            {
                UnWrapItem(existingItem);
                Items.Remove(existingItem);
            }

            Items.Add(item);
            WrapItem(item);


            //if (isStarred)
            //{
            //    existingItem = _starredItems.FirstOrDefault(i => i.Id == item.Id);
            //    if (existingItem != null)
            //    {
            //        _starredItems.Remove(existingItem);
            //    }

            //    AddStarredItem(item);
            //}

            // Starred item cannot be in the unread list
            //if (isStarred == false && item.Unread && loadItemType == LoadItemType.Unread)
            //{
            //    existingItem = _unreadItems.FirstOrDefault(i => i.Id == item.Id);
            //    if (existingItem != null)
            //    {
            //        _unreadItems.Remove(existingItem);
            //        _unreadItems.Add(item);
            //    }
            //    else
            //    {
            //        AddToUnread(item);
            //    }

            //    var date = Int64.Parse(item.TimestampUsec);
            //    if (NewestItemTimestampUsec < date)
            //        NewestItemTimestampUsec = date;
            //}
        }


        public void BeginEdit()
        {
            if (_isEditing)
                throw new InvalidOperationException("Subscription is already editing");

            ServerUnreadsCountChanged = false;
            _isEditing = true;
        }

        public void EndEdit()
        {
            RefreshRequired = false;

            _isEditing = false;
        }

        public void SetLocalCategory(SubscriptionCategory localCategory)
        {
            _categories[0] = localCategory;
        }

        internal void Initialize(SubscriptionsList subscriptionsList)
        {
            Contract.Requires<ArgumentNullException>(subscriptionsList != null, "subscriptionsList");

            SubscriptionsList = subscriptionsList;

            Items = new ObservableCollection<Item>();
            _itemObservers = new Dictionary<Item, PropertyObserver<Item>>();

            Title = WebUtility.HtmlDecode(Title);

        }

        private void WrapItem(Item item)
        {
            var obs = PropertyObserver.Create(item);
            obs.RegisterHandler(i => i.Unread, OnItemReadStateChanged);
            _itemObservers[item] = obs;
        }

        private void UnWrapItem(Item item)
        {
            PropertyObserver<Item> obs;
            if(_itemObservers.TryGetValue(item, out obs))
            {
                obs.UnregisterAllHandlers();
                _itemObservers.Remove(item);
            }
        }

        private void OnItemReadStateChanged(Item item)
        {
            // if it's read now, increment the local read counter

            // Starred items do not affect the count
            if (item.Starred)
                return;

            // Don't process here
            if (_isMarkingAllRead)
                return;

            if(!item.Unread)
            {
                _localReadCount++;
            }
            else
            {
                _localReadCount--;
            }

            RaiseUnreadsChanged();
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext c)
        {
            if (_categories == null || _categories.Count == 0)
            {
                // Add "no category"
                _categories = new List<SubscriptionCategory>
                                  {
                                      new SubscriptionCategory(null)
                                          {
                                              Label = Strings.NoCategory,
                                              Id = SubscriptionsList.NoCategoryId
                                          }
                                  };
            }
        }
    }
}